/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.corn3lius.android.hexagonmadness;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.corn3lius.android.hexagonmadness.R;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.JsonReader;
import android.util.JsonWriter;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;

/**
 * View that draws, takes keystrokes, etc. for a simple Hex Stacking game.
 * 
 * 
 */
public class HexView extends SurfaceView implements SurfaceHolder.Callback {

	/*
	 * This is the enumerated set of directions the board will use
	 */
	public static final int DIR_NONE = 9;
	public static final int DIR_DN = 0;
	public static final int DIR_DN_L = 1;
	public static final int DIR_UP_L = 2;
	public static final int DIR_UP = 3;
	public static final int DIR_UP_R = 4;
	public static final int DIR_DN_R = 5;
	/*
	 * This is the enumerated set of colors to use
	 */
	// public static final int COL_NONE = -1;
	public static final int COL_YELLOW = 0;
	public static final int COL_RED = 1;
	public static final int COL_BLUE = 2;
	public static final int COL_LT_GREEN = 3;
	public static final int COL_PURPLE = 4;
	public static final int COL_ORANGE = 5;
	public static final int COL_LT_BLUE = 6;
	public static final int COL_GREEN = 7;
	public static final int COL_GREY = 8;
	public static final int COL_WHITE = 9;

	private static final int BASE_COLOR_COUNT = 6;
	private static final int MAX_COLOR_COUNT = 10;

	public static final String logId = "hexMadness";

	/*
	 * 
	 * This will be the place holder for multiple uses for the hex like bombs 2X
	 * lightning... etc ?
	 */
	public static final int NORMAL = 0;
	public static final int BOMB = 1;
	public static final int TWO_X = 2;
	public static final int WILD = 3;
	public static final int LIGHTNING = 4;

	/**
	 * Thread -- Main thread
	 * 
	 * @brief This is the where the game logic is
	 * 
	 * @author meb
	 * 
	 */
	public static class GameThread extends Thread {
		/*
		 * Difficulty setting constants
		 */
		public static final int DIFFICULTY_EASY = 0;
		public static final int DIFFICULTY_HARD = 1;
		public static final int DIFFICULTY_MEDIUM = 2;

		/*
		 * State-tracking constants
		 */
		public static final int STATE_LOSE = 1; // Game over // Show menu
		public static final int STATE_PAUSE = 2; // Show menu
		public static final int STATE_IDLE = 3; // InPlay waiting
		public static final int STATE_MOVING = 4; // hex pieces moving
		public static final int STATE_MATCHING = 5; // hex matching will return
													// to moving soon
		public static final int STATE_LEVEL_UP = 6; // running level up sequence
		public static final int STATE_EXIT = 7; // setting this state will
												// trigger the exit dialog.

		/*
		 * Keys for the saved state
		 */
		private static final String KEY_SCORE = "score";
		private static final String KEY_LEVEL = "level";
		private static final String KEY_LAST_MOVED_INDEX = "lastMovedIndex";
		private static final String KEY_INPLAY = "inPlay_";
		private static final String KEY_INPLAY_COUNT = "inPlayCount";
		private static final String KEY_INQUEUE = "inQueue_";
		private static final String KEY_INQUEUE_COUNT = "inQueueCount";
		// This is the format we will store the saved game state in
		// COLOR,DIR,POS.X,POS.Y
		private static final String hexStringFormat = "%d,%d,%d,%d";

		/*
		 * This is the list of hex pieces to populate the board.
		 */
		private List<Hex> inPlay, undoPlay;
		private List<List<Hex>> inQueue, undoQueue;
		private final int MAX_HEX_IN_PLAY = 43;

		/*
		 * Member (state) fields
		 */
		/** The drawable to use as the background of the animation canvas */
		private Bitmap mBackgroundImage;
		private Bitmap mHoverImage;
		private Bitmap mHexSprites;

		private Paint mPaint;

		/** Handle to the application context, used to e.g. fetch Drawables. */
		// private static Context mContext;

		private CharSequence msgLose;
		private CharSequence msgMoving;
		private CharSequence msgIdle;
		private CharSequence msgPause;
		/**
		 * Current height and width of the surface/canvas.
		 * 
		 * @see #setSurfaceSize
		 */
		private int mCanvasHeight = 1;
		private int mCanvasWidth = 1;
		private PointF mCenter = new PointF();

		/**
		 * Hex height and width Modify these for different size screens.
		 */
		private int mHexHeight = 52;
		private int mHalfHexHeight = mHexHeight / 2;
		private int mHexWidth = 43; // this is the offset from point to top
		private int mHalfHexWidth = mHexWidth / 2;

		/**
		 * This is the list of queue locations for the pieces to enter the board
		 */
		private List<List<Point>> boardQueues;
		// private int inPlayCount;

		/** Message handler used by thread to interact with TextView */
		private Handler mHandler;

		private long mLastTime;
		private int mScore;
		private int mLevel;
		private int mLastMovedIndex;
		private int mColorCount;
		// private int colorCounter;

		private int _lastsize = 0;

		private int levelUp = 0;

		// private static int _index;

		/** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
		private int mMode;

		/** Handle to the surface manager object we interact with */
		private SurfaceHolder mSurfaceHolder;

		/** Indicate whether the surface has been created & is ready to draw */
		private boolean mRun = false;

		private List<PointF> mFloatingCircles;
		private final int mFloatCircleCount = 20;

		private PointF mMove;
		private PointF mHover;
		private PointF ghostPoint;
		private final PointF mOrigin = new PointF(0, 0);

		public GameThread(SurfaceHolder surfaceHolder, Context context,
				Handler handler) {
			// get handles to some important objects
			mSurfaceHolder = surfaceHolder;
			mHandler = handler;
			// mContext = context;

			Resources res = context.getResources();

			msgLose = res.getText(R.string.mode_gameover);
			msgMoving = res.getText(R.string.mode_moving);
			msgIdle = res.getText(R.string.mode_idle);
			msgPause = res.getText(R.string.mode_paused);

			// load background image
			mBackgroundImage = BitmapFactory.decodeResource(res,
					R.drawable.background);
			mHoverImage = BitmapFactory.decodeResource(res,
					R.drawable.hex_hover);

			// get the sprite sheet.
			mHexSprites = BitmapFactory.decodeResource(res,
					R.drawable.hex_pieces_hdpi);

			// Build the queue lists.
			// this list of lists will be queue clock wise starting at the top
			// and working around
			// following the DIR_ of the hex direction
			boardQueues = new ArrayList<List<Point>>();
			// add down
			List<Point> down = new ArrayList<Point>();
			down.add(new Point(4, 1));
			down.add(new Point(5, 2));
			down.add(new Point(6, 1));
			down.add(new Point(7, 2));
			down.add(new Point(8, 1));
			boardQueues.add(down);
			// add down left
			List<Point> dn_l = new ArrayList<Point>();
			dn_l.add(new Point(9, 2));
			dn_l.add(new Point(9, 3));
			dn_l.add(new Point(10, 3));
			dn_l.add(new Point(10, 4));
			dn_l.add(new Point(11, 5));
			boardQueues.add(dn_l);
			// Add up left
			List<Point> up_l = new ArrayList<Point>();
			up_l.add(new Point(11, 6));
			up_l.add(new Point(10, 6));
			up_l.add(new Point(10, 7));
			up_l.add(new Point(9, 8));
			up_l.add(new Point(9, 9));
			boardQueues.add(up_l);
			// add up
			List<Point> up = new ArrayList<Point>();
			up.add(new Point(8, 9));
			up.add(new Point(7, 9));
			up.add(new Point(6, 9));
			up.add(new Point(5, 9));
			up.add(new Point(4, 9));
			boardQueues.add(up);
			// add up right
			List<Point> up_r = new ArrayList<Point>();
			up_r.add(new Point(3, 9));
			up_r.add(new Point(3, 8));
			up_r.add(new Point(2, 7));
			up_r.add(new Point(2, 6));
			up_r.add(new Point(1, 6));
			boardQueues.add(up_r);
			// add down right
			List<Point> dn_r = new ArrayList<Point>();
			dn_r.add(new Point(1, 5));
			dn_r.add(new Point(2, 4));
			dn_r.add(new Point(2, 3));
			dn_r.add(new Point(3, 3));
			dn_r.add(new Point(3, 2));
			boardQueues.add(dn_r);

			// Create the lists we will be working with.
			inQueue = new ArrayList<List<Hex>>();
			inQueue.add(new ArrayList<Hex>());
			inQueue.add(new ArrayList<Hex>());
			inQueue.add(new ArrayList<Hex>());
			undoQueue = new ArrayList<List<Hex>>();
			undoQueue.add(new ArrayList<Hex>());
			undoQueue.add(new ArrayList<Hex>());
			undoQueue.add(new ArrayList<Hex>());

			inPlay = new ArrayList<Hex>();
			undoPlay = new ArrayList<Hex>();

			loadGame();
			// add hex to the Queues
			initQueues();
			// add inPlay items
			//resetInPlay();

			mMove = new PointF(0, 0);
			mHover = new PointF(0, 0);
			ghostPoint = new PointF(0, 0);

			// TODO changes these DEBUG VALUES
			mColorCount = BASE_COLOR_COUNT;
			//mScore = 0;
			//mLevel = 1;

			// Set the basic Paint class we will use later.
			mPaint = new Paint();

			mPaint.setColor(Color.WHITE);
			mPaint.setAlpha(255);
			mPaint.setTextSize(30);
			mPaint.setTextAlign(Paint.Align.RIGHT);
			mPaint.setStyle(Paint.Style.STROKE);

			mFloatingCircles = new ArrayList<PointF>(mFloatCircleCount);

			doStart();
		}

		/**
		 * take some arg to determine the colors in the queues int lists ? for
		 * just color ? direction is given TODO
		 * */
		private void initQueues() {
			for (List<Point> q : boardQueues) {
				for (Point p : q) {
					int color_r = (int) (Math.random() * BASE_COLOR_COUNT);

					inQueue.get(0).add(
							new Hex(color_r, boardQueues.indexOf(q), p));
					color_r = (int) (Math.random() * BASE_COLOR_COUNT);
					inQueue.get(1).add(
							new Hex(color_r, boardQueues.indexOf(q), p));
					color_r = (int) (Math.random() * BASE_COLOR_COUNT);
					inQueue.get(2).add(
							new Hex(color_r, boardQueues.indexOf(q), p));
				}
			}
			undoQueue.clear();
			undoQueue.addAll(inQueue);
		}

		/**
		 * take some arg to tell us how to set it up TODO
		 * 
		 * */
		public void resetInPlay() {
			inPlay.clear();
			mScore = 0;
			mLevel = 1;
			initInPlay(0);
		}

		private void initInPlay(int level) {

			List<Point> p = Arrays.asList(new Point(5, 5), new Point(7, 5),
					new Point(5, 6), new Point(6, 6), new Point(6, 7),
					new Point(6, 4), new Point(6, 5), new Point(4, 6),
					new Point(4, 4), new Point(6, 7), new Point(6, 3),
					new Point(8, 5));
			int hexCount = 4 + (level % 5);
			if (((level / 5.0) % 5) == 0) {
				mColorCount = (mColorCount < MAX_COLOR_COUNT) ? mColorCount + 1
						: MAX_COLOR_COUNT;
			}
			for (int i = 0; i < hexCount; i++) {
				int color = (int) (Math.random() * BASE_COLOR_COUNT);
				inPlay.add(new Hex(color, p.get(i)));
			}
			undoPlay.clear();
			undoPlay.addAll(inPlay);

		}

		/**
		 * This function takes the grid point and returns the actual screen
		 * location to draw
		 * 
		 * @param row
		 * @param col
		 * @return actual screen location.
		 */
		private PointF getHexCoords(int col, int row) {
			// this is the height of the grid + half of the size if it is in the
			// odd rows
			// oops!
			float y = (float) (((row - 1) * mHexHeight) + ((col % 2) == 0 ? mHexHeight / 2.0
					: 0)) - 17;
			// this is the width from the left most point to the top of the hex
			// where the next point will start
			float x = 109 + (float) (col * mHexWidth);
			PointF newP = new PointF(x, y);
			return newP;
		}

		/**
		 * Helper function for above allows the caller to call it with a known
		 * point as opposed to an x and y value.
		 * 
		 * @param p
		 *            the Point in the grid to get the float position
		 * @return PointF of the actual screen coords
		 */
		private PointF getHexCoords(Point p) {
			return getHexCoords(p.x, p.y);
		}

		/**
		 * Starts the game, setting parameters for the current difficulty.
		 */
		public void doStart() {
			synchronized (mSurfaceHolder) {
				// Start the game and go to the menu first
				setState(STATE_IDLE);
			}
		}

		/**
		 * Pauses the physics update & animation.
		 */
		public void pause() {
			synchronized (mSurfaceHolder) {
				if (mMode == STATE_IDLE) {
					setState(STATE_PAUSE);
				}
			}
		}

		/**
		 * Resumes from a pause.
		 */
		public void unpause() {
			// Move the real time clock up to now
			synchronized (mSurfaceHolder) {
				mLastTime = System.currentTimeMillis() + 100;
			}
			setState(STATE_IDLE);
		}

		private void saveGame() {
			Log.e(logId, "Save the thread game states to - " + mContext.getFilesDir() );
//			File json = new File(mContext.getFilesDir(), "hexsave.json");
//			try {
//				json.createNewFile();
//			} catch (IOException e1) {
//				// TODO Auto-generated catch block
//				e1.printStackTrace();
//			}
//			if( json.exists() )				
//			{
//				Log.w(logId, "Save game found ");				
//			}

			JsonWriter jwriter;
			try {
				jwriter = new JsonWriter(new BufferedWriter(new FileWriter( new File(mContext.getFilesDir(), "hexsave.json"))));
			
				jwriter.setIndent("  ");
				jwriter.beginObject(); // Start all game info 
				Log.e(logId, String.format("-----  save meta data  %d - %d ", mScore, mLevel )); 
				jwriter.name("score").value(mScore); 
				jwriter.name("level").value(mLevel);
				// Write the list of the inplay hex. 
				jwriter.name("inplay");
				for(Hex h : inPlay ){
					Log.e(logId, String.format("-----  save h(%d,%d,[%d,%d])",h.color, h.direction, h.position.x, h.position.y));
					jwriter.beginObject(); // start hex object. 
					jwriter.name("color").value(h.color);
					jwriter.name("direction").value(h.direction);
					jwriter.name("type").value(h.type);
					jwriter.name("pos");
					jwriter.beginArray();
					jwriter.value(h.position.x);
					jwriter.value(h.position.y);
					jwriter.endArray();  // end pos array
					jwriter.endObject(); // end hex object
				}
				// Write the list of the queues. 
				//jwriter.name("inqueue0");
				//for(Hex h : inQueue.get(0) ){
					
				//}
				
				jwriter.endObject();
				jwriter.close();
				
			} catch (IOException e) {
				Log.e(logId, "JSON save exception " + e);
			} 
		}

		public void loadGame() {
			Log.e(logId, "Load the thread game states");

			JsonReader jreader;
			try {
				jreader = new JsonReader(new BufferedReader(new FileReader(new File(mContext.getFilesDir(), "hexsave.json"))));
			
				jreader.beginObject();
				while (jreader.hasNext()) {
					String name = jreader.nextName();
					if( name.equals("score")){
						mScore = jreader.nextInt(); 
					}
					else if ( name.equals("level")){
						mLevel = jreader.nextInt(); 
					}
					else if ( name.equals("inplay")){
						jreader.beginObject();
						Hex h = new Hex(); 
						while ( jreader.hasNext() )	{
							name = jreader.nextName(); 
							if( name.equals("direction")){
								h.direction = jreader.nextInt(); 
							}
							else if ( name.equals("color")){
								h.color = jreader.nextInt(); 
							}
							else if (name.equals("type")){
								h.type = jreader.nextInt();
							}
							else if( name.equals("pos") ){
								jreader.beginArray();
								int x = jreader.nextInt();
								int y = jreader.nextInt();
								h.position = new Point(x,y);
								jreader.endArray();
							}
						}
						jreader.endObject();
					}
					//else if( name.equals("inqueue")){
						
					//}
					
				}
				jreader.endObject(); 
				
				jreader.close();
			} catch (Exception e) {
				Log.e(logId, "JSON read exception " + e);
				resetInPlay();				
			}

		}

		@Override
		public void run() {
			// long last = System.currentTimeMillis();
			// int frames = 0;
			Canvas c = null;
			while (mRun) {
				try {
					c = mSurfaceHolder.lockCanvas(null);
					synchronized (mSurfaceHolder) {
						/*
						 * Add this to the debug output Frame Rate
						 * ------------------
						 * 
						 * long now = System.currentTimeMillis(); frames++; if
						 * (now - last >= 1000) { mLevel = (int) (frames * 1000
						 * / (now - last)); last = now; frames = 0; }
						 */

						updateHex();
						doDraw(c);
					}
				} finally {
					// do this in a finally so that if an exception is thrown
					// during the above, we don't leave the Surface in an
					// inconsistent state
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
				}
			}

		}

		/**
		 * Used to signal the thread whether it should be running or not.
		 * Passing true allows the thread to run; passing false will shut it
		 * down if it's already running. Calling start() after this was most
		 * recently called with false will result in an immediate shutdown.
		 * 
		 * @param b
		 *            true to run, false to shut down
		 */
		public void setRunning(boolean b) {
			mRun = b;
		}

		/**
		 * Sets the game mode. That is, whether we are running, paused, in the
		 * failure state, in the victory state, etc.
		 * 
		 * @see #setState(int, CharSequence)
		 * @param mode
		 *            one of the STATE_* constants
		 */
		public void setState(int mode) {
			setState(mode, null);
		}

		/**
		 * Sets the game mode. That is, whether we are running, paused, in the
		 * failure state, in the victory state, etc.
		 * 
		 * @param mode
		 *            one of the STATE_* constants
		 * @param message
		 *            string to add to screen or null
		 */
		public void setState(int mode, CharSequence message) {
			/*
			 * This method optionally can cause a text message to be displayed
			 * to the user when the mode changes. Since the View that actually
			 * renders that text is part of the main View hierarchy and not
			 * owned by this thread, we can't touch the state of that View.
			 * Instead we use a Message + Handler to relay commands to the main
			 * thread, which updates the user-text View.
			 */
			synchronized (mSurfaceHolder) {
				mMode = mode;

				CharSequence str = "";
				Message msg = mHandler.obtainMessage();
				Bundle b = new Bundle();
				if (message == null) {
					message = "";
				}
				switch (mMode) {
				case STATE_MOVING:
					str = msgMoving;
					break;
				case STATE_IDLE:
					str = msgIdle;
					break;
				case STATE_PAUSE:
					str = msgPause;
					break;
				case STATE_LOSE:
					str = msgLose;
					break;
				}

				// send a message to the parent to set stuff in its view
				// [text] = str
				b.putInt("state", mMode);
				b.putString("text", message.toString() + str.toString());

				msg.setData(b);
				mHandler.sendMessage(msg);

			}
		}

		/* Callback invoked when the surface dimensions change. */
		public void setSurfaceSize(int width, int height) {
			// synchronized to make sure these all change atomically
			synchronized (mSurfaceHolder) {
				mCanvasWidth = width;
				mCanvasHeight = height;
				mCenter = new PointF(width / 2, height / 2);
				Log.d(logId, "Screen size " + height + " , " + width);
				// don't forget to resize the background image
				// TODO handle operations in portrait mode.
				mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage,
						width, height, true);

				mFloatingCircles.clear();
				for (int f = 0; f < mFloatCircleCount; f++) {
					float x = (float) (Math.random() * mCanvasWidth);
					float y = (float) (Math.random() * mCanvasHeight);
					mFloatingCircles.add(new PointF(x, y));
				}

			}
		}

		/**
		 * 
		 * Canvas.
		 */
		private void doDraw(Canvas canvas) {
			// /
			if (canvas == null)
				return; // OH SHIT!!
			//
			// Draw the background image. Operations on the Canvas accumulate
			// so this is like clearing the screen.
			// int r = 195;
			int _color = 255 * inPlay.size() / MAX_HEX_IN_PLAY;
			canvas.drawARGB(196, _color, 0, 0);

			int idx = 0;
			int radius = 140;
			int diameter = 280;
			for (PointF p : mFloatingCircles) { // int l = 0; l <
												// mFloatCircleCount; l++) {

				// PointF p = mFloatingCircles.get(l);
				switch (idx++ % 6) {
				case 0:
					p.x += 0.5;
					p.y += 1;
					break;
				case 1:
					p.x += 1;
					p.y += 0.5;
					break;
				case 2:
					p.y += 1;
					break;
				case 3:
					p.y -= 1;
					break;
				case 4:
					p.x -= 1;
					p.y -= 0.5;
					break;
				case 5:
					p.x -= 0.5;
					p.y -= 1;
					break;
				}
				if (p.x > mCanvasWidth + radius)
					p.x = -radius;
				else if (p.x < -radius)
					p.x = mCanvasWidth + radius;

				if (p.y > mCanvasHeight + radius)
					p.y = -radius;
				else if (p.y < -radius)
					p.y = mCanvasHeight + radius;
				mPaint.setAlpha((int) (255 - (1 / distance_sq(p, mCenter))));
				canvas.drawCircle(p.x, p.y, diameter, mPaint);
			}

			canvas.drawBitmap(mBackgroundImage, 0, 0, null);

			// TODO re-position these to optimal areas
			canvas.drawText(String.format(" %d", mScore), (float) 100.0,
					(float) 100.0, mPaint);
			canvas.drawText(String.format(" %d", mLevel), (float) 700.0,
					(float) 100.0, mPaint);

			// TODO handle states better.
			switch (mMode) {
			case STATE_LEVEL_UP:
				// pop up :: hey you leveled up dude..?

				// add drawn features for other states...
				break;
			case STATE_LOSE:
				// Lame ..

				break;

			}

			canvas.save();
			PointF point;
			Rect s;
			RectF d;
			Paint ghostPaint = new Paint(mPaint);
			ghostPaint.setAlpha(128);
			d = new RectF(0, 0, mHexHeight + 3, mHexWidth + 5);
			for (Hex h : inQueue.get(2)) {
				point = getHexCoords(h.position.x, h.position.y);
				if (h.direction == DIR_UP || h.direction == DIR_UP_L
						|| h.direction == DIR_UP_R) {
					point.y += 20;
				} else if (h.direction == DIR_DN || h.direction == DIR_DN_L
						|| h.direction == DIR_DN_R) {
					point.y -= 20;
				}
				if (h.direction == DIR_UP_L || h.direction == DIR_DN_L) {
					point.x += 30;
				} else if (h.direction == DIR_UP_R || h.direction == DIR_DN_R) {
					point.x -= 30;
				}
				s = h.getSrc();
				d.offsetTo(point.x, point.y);
				canvas.drawBitmap(mHexSprites, s, d, ghostPaint);
			}

			ghostPaint.setAlpha(196);
			for (Hex h : inQueue.get(1)) {
				point = getHexCoords(h.position.x, h.position.y);
				if (h.direction == DIR_UP || h.direction == DIR_UP_L
						|| h.direction == DIR_UP_R) {
					point.y += 10;
				} else if (h.direction == DIR_DN || h.direction == DIR_DN_L
						|| h.direction == DIR_DN_R) {
					point.y -= 10;
				}
				if (h.direction == DIR_UP_L || h.direction == DIR_DN_L) {
					point.x += 15;
				} else if (h.direction == DIR_UP_R || h.direction == DIR_DN_R) {
					point.x -= 15;
				}
				s = h.getSrc();
				d.offsetTo(point.x, point.y);
				canvas.drawBitmap(mHexSprites, s, d, ghostPaint);

			}
			// for the hover
			ghostPaint.setAlpha(30);
			for (Hex h : inQueue.get(0)) {

				point = getHexCoords(h.position.x, h.position.y);

				s = h.getSrc();
				d.offsetTo(point.x, point.y);
				canvas.drawBitmap(mHexSprites, s, d, null);

				if (h.inHover) {
					canvas.drawBitmap(mHoverImage, point.x - 4, point.y - 5,
							null);
					ghostPaint.setAlpha(30);

					d.offsetTo(ghostPoint.x, ghostPoint.y);
					canvas.drawBitmap(mHexSprites, s, d, ghostPaint);

				}
			}
			for (Hex h : inPlay) {
				point = getHexCoords(h.position.x, h.position.y);

				s = h.getSrc();
				d.offsetTo(point.x, point.y);
				canvas.drawBitmap(mHexSprites, s, d, null);

				if (h.direction != DIR_NONE) {
					// draw the direction on the hex. ---
					s = h.getDirSrc();
					canvas.drawBitmap(mHexSprites, s, d, null);
				}
			}
			canvas.restore();
		}

		/**
		 * Real time updates of the Hex Board
		 */
		private void updateHex() {
			long now = System.currentTimeMillis();

			// Do nothing if mLastTime is in the future.
			// This allows the game-start to delay the start of the physics
			// by 100ms or whatever.
			if (mLastTime > now)
				return;

			switch (mMode) {
			/**
			 * This state has all the pieces in play and if they move we will
			 * update the position one point at a time. And requeue any hex that
			 * make it across the board.
			 */
			case STATE_MOVING:
				// keep all the movable pieces moving.
				int count = movePieces();
				if (count == 0) {
					setState(STATE_MATCHING);
				}
				break;
			case STATE_MATCHING:
				int matches = checkForMatches();
				// ERASE matches
				if (matches == 0) {
					setState(STATE_IDLE);
				} else {
					setState(STATE_MOVING);
				}
				break;
			/**
			 * This state is looking for touched events and will trigger the
			 * release of a queued hex. we will check the closest touched hex
			 * for its availability to leave the queue
			 */
			case STATE_IDLE:
				// check for touched events and movable queued hex
				if (checkForInitialMove()) {
					setState(STATE_MOVING, msgMoving);
				}
				break;

			case STATE_LEVEL_UP:
				levelUp++;
				if (levelUp >= 50) {
					levelUp = 0;
					setState(STATE_IDLE);
					initInPlay(++mLevel);
				}

				break;
			}
			if (inPlay.isEmpty()) {
				// This should trigger next level...
				setState(STATE_LEVEL_UP);

			} else if (inPlay.size() >= 43) {
				// board is filled
				setState(STATE_LOSE, msgLose);

			} else {
				if (_lastsize != inPlay.size()) {
					_lastsize = inPlay.size();
					Log.d(logId,
							String.format("inPlay count %d", inPlay.size()));
				}
			}

			// UPDATE HEX BMP OVERLAYS ?

		}

		/**
		 * This function should be called from the main game loop to update all
		 * pieces on the board when the state is set to moving.
		 */
		private int movePieces() {
			// For all the hex in the list
			int moveCount = 0;
			List<Point> hexMoves = new ArrayList<Point>();
			List<Hex> requeueList = new ArrayList<Hex>();

			for (Hex h : inPlay) {
				// if the hex is on the board and has a direction to go
				// Find out where the next position is
				Point newPos = h.getNextPosition();
				// See if it can be moved there.

				boolean canBeMoved = h.direction != DIR_NONE;
				for (Hex h2 : inPlay) {
					if (newPos.equals(h2.position.x, h2.position.y)) {
						canBeMoved = false;
						break;
					}
				}
				// move it
				if (newPos.x > 20 || newPos.x < 0 || newPos.y < 0
						|| newPos.y > 20) {
					Log.d(logId, "Error in next position. ");

				} else if (canBeMoved) {
					if (boardQueues.get((h.direction + 3) % 6).contains(newPos)) {
						Log.d(logId,
								String.format("Requeued hex -- %d", moveCount));
						requeueList.add(h);
					} else {
						h.move(newPos);
					}
					hexMoves.add(newPos);

					moveCount++;
				}

			}
			for (Hex h : requeueList) {
				requeue(h);
			}
			// Log.i("hexMadness",String.format("%d in play %d moved",
			// inPlayCount, moveCount));
			// / //// // / // / / // /
			// for (Point p : hexMoves) {
			// Log.d(logId, String.format(" Moved hex to ( %d, %d)", p.x, p.y));
			// }
			// Log.d(logId, String.format("Moved %d hex", moveCount));
			return moveCount;
		}

		/**
		 * see the updateHex for my usage.
		 * 
		 */
		private boolean checkForInitialMove() {
			Hex movedHex = null;
			double dist = 1000;
			PointF hexPos = null;

			for (Hex h : inQueue.get(0)) {
				// mMove means the user released a point on the board this
				// will trigger
				// the closest piece to enter the board if it can stop on
				// the board.

				h.inHover = false;
				if (!mMove.equals(0, 0)) {
					hexPos = getHexCoords(h.position.x, h.position.y);
					dist = distance_sq(hexPos, mMove);
					// if its within the area touched
					if (dist < mHalfHexWidth * mHalfHexWidth) {

						if (moveAvailable(h) == null) {
							Log.i(logId, "move not available");
							continue;
						}
						// set this as the one to play
						movedHex = h;
						break;
					}
				} else if (!mHover.equals(0, 0)) {
					hexPos = getHexCoords(h.position.x, h.position.y);
					dist = distance_sq(hexPos, mHover);
					if (dist < mHalfHexWidth * mHalfHexWidth) {
						h.inHover = true;
						Point pos = moveAvailable(h);
						if (pos != null) {
							ghostPoint = getHexCoords(pos);
						}
						break;
					}
				}
			}
			mMove.set(mOrigin);
			if (movedHex != null) {

				dequeue(movedHex);

				return true;
			}
			return false;
		}

		/**
		 * This function looks for all the hex in play and erases all matching
		 * sets of three or more.
		 * 
		 * @return
		 */
		private int checkForMatches() {

			List<Hex> matchList = new ArrayList<Hex>(6);
			List<Hex> largestSet = new ArrayList<Hex>(6);
			// for all hex build
			for (Hex h : inPlay) {
				// this matchlist to erase the matching hex.

				matchList = GetMatchList(h, matchList);

				if (matchList.size() >= largestSet.size()) {
					largestSet.clear();
					largestSet.addAll(matchList);
					Log.i(logId,
							String.format("Match Count %d color %d",
									matchList.size(), matchList.get(0).color));
				}
				matchList.clear();

			}
			if (largestSet.size() >= 3) {
				matchList.addAll(largestSet);
				for (Hex matched : matchList) {
					// remove the matched hex for the inPlay list.
					if (inPlay.remove(matched)) {
						Log.d(logId, String.format(
								"Cleared hex color[%d] at (%d,%d) ",
								matched.color, matched.position.x,
								matched.position.y));
					}
				}
			}
			return matchList.size();
		}

		/**
		 * Helper function for getting the matches for the hex. This is a
		 * recursive function. recursive function.
		 * 
		 * @param thisHex
		 *            - object to test against the list inPlay
		 * @param thisList
		 *            - set of hex matching so far.
		 * @return the list given to us and if we add this hex to the list .
		 */
		private List<Hex> GetMatchList(Hex thisHex, List<Hex> thisList) {

			// add the incoming hex to the incoming list.
			if (!thisList.contains(thisHex)) {
				thisList.add(thisHex);
			}
			for (Hex h : inPlay) {
				// check if 'h' and 'thishex' are close
				if (thisHex.inSetWith(h)) {
					// if 'h' isnt in the list of matches add it
					if (!thisList.contains(h)) {
						thisList.add(h);
						// check all the matches around this new one.
						GetMatchList(h, thisList);
					}
				}
			}
			return thisList;
		}

		/**
		 * requeue this function is called when the piece in play successfully
		 * goes across the field and needs to be re-queued on the other side.
		 * 
		 * @param h
		 *            - this is the piece that has traveled across the board and
		 *            needs to reenter the queue.
		 */
		private void requeue(Hex h) {

			h.move(h.getNextPosition());
			int index = -1;
			for (Hex h2 : inQueue.get(0)) {
				if (h2.position.equals(h.position.x, h.position.y)) {
					Log.d(logId, "Match in requeue ");

					index = inQueue.get(0).indexOf(h2);
					break;
					// // move h2, ...
				}
			}
			if (index == -1) {
				Log.d(logId, "Error in re-queuing a hex? ");
				return;
			}

			inQueue.get(2).remove(index);
			inQueue.get(2).add(index, inQueue.get(1).remove(index));
			inQueue.get(1).add(index, inQueue.get(0).remove(index));
			inQueue.get(0).add(index, h);

			inPlay.remove(h);

			h.direction = (h.direction + 3) % 6; // flip the direction.
		}

		/**
		 * this will be called to remove a hex from the queue lists and all the
		 * subsequent hex in the lists will be shifted
		 * 
		 * @param h
		 *            this is the actual hex being set in play
		 */
		private void dequeue(Hex h) {

			int index = inQueue.get(0).indexOf(h);

			undoQueue.clear();
			undoQueue.add(new ArrayList<Hex>());
			for (Hex h_2 : inQueue.get(0)) {
				undoQueue.get(0).add(new Hex(h_2));
			}
			undoQueue.add(new ArrayList<Hex>());
			for (Hex h_2 : inQueue.get(1)) {
				undoQueue.get(1).add(new Hex(h_2));
			}
			undoQueue.add(new ArrayList<Hex>());
			for (Hex h_2 : inQueue.get(2)) {
				undoQueue.get(2).add(new Hex(h_2));
			}

			undoPlay.clear();
			undoPlay = new ArrayList<Hex>();
			for (Hex h_2 : inPlay) {
				undoPlay.add(new Hex(h_2));
			}
			Log.d(logId, String.format("%s I - %s U",
					inQueue.get(0).get(index), undoQueue.get(0).get(index)));

			// add new Hex to the list and put it in the
			// current hex's spot
			Hex newHex = new Hex((int) (Math.random() * mColorCount),
					h.position);
			newHex.direction = h.direction;
			// disable inQueue add inPlay
			h.setInPlay(h.getNextPosition(), h.direction);
			// add new hex to the queue and main list
			inQueue.get(0).remove(h);
			inQueue.get(0).add(index, inQueue.get(1).remove(index));
			inQueue.get(1).add(index, inQueue.get(2).remove(index));
			inQueue.get(2).add(index, newHex);

			inPlay.add(h);

		}

		/**
		 * 
		 */
		private double distance_sq(PointF a, PointF b) {
			return ((a.x - b.x) * (a.x - b.x)) + ((a.y - b.y) * (a.y - b.y));
		}

		/**
		 * moveAvailable
		 * 
		 * @param pos
		 *            the starting position
		 * @return the hex position for the shadow. if it is (0,0) the move
		 *         isn't valid
		 */
		private Point moveAvailable(Hex hex) {
			// get the list of points the hex will travel.
			List<Point> pointList = hex.getPositionArray();
			for (Point p : pointList) {
				for (Hex h : inPlay) {
					// only check the hex in play to stop behind.
					if (p.equals(h.position.x, h.position.y)) {
						int index = pointList.indexOf(p);
						if (index == 0)
							return null;
						else
							return pointList.get(index - 1);
					}
				}
			}
			return null;
		}

		/**
		 * This is the call to run the undo command one level to remove a
		 * un-intended move.
		 */
		public void undo() {
			inPlay.clear();
			inPlay.addAll(undoPlay);
			inQueue.clear();
			inQueue.addAll(undoQueue);

		}

		/**
		 * this is the Thread onTouch event handler
		 * 
		 * @param motion
		 * @return
		 */
		public boolean onTouch(MotionEvent motion) {
			if (mMode == STATE_IDLE) {
				int action = motion.getAction();
				float x = motion.getRawX() - mHalfHexWidth;
				float y = motion.getRawY() - mHalfHexHeight;
				switch (action) {
				case MotionEvent.ACTION_UP:
					// select the piece to move
					mMove.set(x, y);
					// this is used to move the touched position to the top
					mHover.set(mOrigin);
					ghostPoint.set(mOrigin);
					// Log.i(logId, String.format("set move  - (%3.3f,%3.3f)",
					// mMove.x, mMove.y));
					break;
				case MotionEvent.ACTION_MOVE:
				case MotionEvent.ACTION_DOWN:
					// add hover light up, path illumination , ghost piece.
					mHover.set(x, y);
					// Log.i(logId, String.format("set hover - (%3.3f,%3.3f)",
					// mHover.x, mHover.y));
					break;
				}

			}
			return true;
		}

	}

	/** Handle to the application context, used to e.g. fetch Drawables. */
	private static Context mContext;

	/** Pointer to the text view to display "Paused.." etc. */
	private static TextView mStatusText;
	private static ImageButton mMenuBtn;
	private ImageButton mUndoBtn;

	/** The thread that actually draws the animation */
	private static GameThread thread = null;
	public boolean exiting = false;

	private static Handler mMsgHandler = new Handler() {

		@Override
		public void handleMessage(Message m) {
			if (thread == null)
				return;
			if (m.getData().getInt("state") == GameThread.STATE_PAUSE) {
				// mMenuBtn.performClick();
				// } else if (m.getData().getInt("state") ==
				// GameThread.STATE_EXIT) {
				// AlertDialog.Builder builder = new
				// AlertDialog.Builder(mContext);
				// builder.setMessage("Are you sure you want to exit?")
				// .setCancelable(false)
				// .setPositiveButton("Yes",
				// new DialogInterface.OnClickListener() {
				// @Override
				// public void onClick(DialogInterface dialog,
				// int id) {
				// thread.setRunning(false);
				// }
				// })
				// .setNegativeButton("No",
				// new DialogInterface.OnClickListener() {
				// @Override
				// public void onClick(DialogInterface dialog,
				// int id) {
				// dialog.cancel();
				// thread.setState(GameThread.STATE_IDLE);
				// }
				// });
				// AlertDialog alert = builder.create();
				// alert.show();
			}
			mStatusText.setVisibility(INVISIBLE);
			// mStatusText.setText(m.getData().getString("text"));
		}
	};

	/**
	 * Constructor er...
	 * 
	 * @param context
	 * @param attrs
	 */
	public HexView(Context context, AttributeSet attrs) {
		super(context, attrs);

		mContext = context;
		// register our interest in hearing about changes to our surface
		SurfaceHolder holder = getHolder();
		holder.addCallback(this);
		Log.i(logId, " HexView Created. ");

		// create thread only; it's started in surfaceCreated()
		thread = new GameThread(holder, mContext, mMsgHandler);

		setFocusable(true); // make sure we get key events

	}

	/**
	 * Fetches the animation thread corresponding to this LunarView.
	 * 
	 * @return the animation thread
	 */
	public final GameThread getThread() {
		return thread;
	}

	/**
	 * Standard window-focus override. Notice focus lost so we can pause on
	 * focus lost. e.g. user switches to take a call.
	 */
	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		Log.d("hexMadness", String.format(" ---  we %s focus",
				(hasWindowFocus) ? "HAVE" : "DON'T HAVE"));
		if (thread != null) {
			if (hasWindowFocus) {
				switch (thread.getState()) {
				case NEW:
					Log.d("hexMadness", "thread NEW");
					thread.start();
					break;
				case RUNNABLE:
					Log.d("hexMadness", "thread RUNNABLE");
					// thread.run();
					break;

				case TERMINATED:
					Log.e("hexMadness", "thread Terminated");
					break;
				case WAITING:
					Log.d("hexMadness", "thread WAITING");
					thread.unpause();

					break;
				case BLOCKED:
					Log.d("hexMadness", "thread BLOCKED");

					break;
				default:

					Log.d("hexMadness", "thread : default? ");
					break;
				}

			} else {
				Log.w(logId, "Focus has changed,.    -- pause it ? ");
				if (thread != null) {
					thread.pause();
				}
			}
		} else {
			Log.e("hexMadness", " thread null ----");
		}
	}

	/**
	 * This function will handle the touch events
	 * 
	 */
	@Override
	public boolean onTouchEvent(MotionEvent motion) {

		if (thread == null) {
			return false;
		}
		// Log.i(logId, String.format("%d action ", motion.getAction()));
		return thread.onTouch(motion);
	}

	/**
	 * Installs a pointer to the view items.
	 */
	public void setTextView(TextView textView) {
		mStatusText = textView;
	}

	public void setMenuButton(ImageButton btn) {
		mMenuBtn = btn;
		mMenuBtn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {

				if (thread.getState() == Thread.State.WAITING) {
					return;
				}

				Log.d(logId, "Touched Menu");

				thread.pause();
				AlertDialog.Builder builder = new AlertDialog.Builder(mContext);

				// builder.setTitle("Title ... ");

				builder.setItems(new CharSequence[] { "Continue", "Options",
						"Reset" }, new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						switch (which) {
						case 0:
							thread.unpause();
							break;
						case 1:

							break;
						case 2:
							thread.resetInPlay();
							thread.unpause();
							break;
						}
					}
				});
				builder.show();

			}
		});
	}

	public void setUndoButton(ImageButton btn) {
		mUndoBtn = btn;

		mUndoBtn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Log.d(logId, "Touched Undo");
				thread.undo();
			}
		});
	}

	/* Callback invoked when the surface dimensions change. */
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		Log.d(logId, String.format("Surface size change %d,%d", width, height));
		if (thread != null) {
			thread.setSurfaceSize(width, height);
		}
	}

	/*
	 * Callback invoked when the Surface has been created and is ready to be
	 * used.
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// start the thread here so that we don't busy-wait in run()
		// waiting for the surface to be created
		Log.d(logId, "Surface created");
		if (thread.getState() == State.TERMINATED) {
			Log.e(logId, "Thread is null on restart --- Create another one. ");
			thread = new GameThread(getHolder(), mContext, mMsgHandler);
		}
		thread.setRunning(true);
	}

	/*
	 * Callback invoked when the Surface has been destroyed and must no longer
	 * be touched. WARNING: after this method returns, the Surface/Canvas must
	 * never be touched again!
	 */
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// we have to tell thread to shut down & wait for it to finish, or else
		// it might touch the Surface after we return and explode
		boolean retry = true;
		Log.d(logId, String.format("Surface Destroyed"));
		thread.saveGame();
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
				Log.d(logId, "exception in surface destroyed");
			}
		}
	}

}
